package org.scaffold.jdbc;

import org.scaffold.utils.ClassUtils;
import org.scaffold.utils.LogUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.*;
import java.util.Date;

/**
 * 数据库助手
 */
public class DbHandle {

    private static DatabaseType databaseType;
    private static final Map<String, Class> CLASS_NAME = new LinkedHashMap<>();
    //    private static final Map<String, Integer> JDBC_TYPE = new LinkedHashMap<>();
    private static final Properties TYPES_CONFIG = new Properties();

    static {

        CLASS_NAME.put(Boolean.class.getName(), Boolean.class);
        CLASS_NAME.put(Integer.class.getName(), Integer.class);
        CLASS_NAME.put(Long.class.getName(), Long.class);
        CLASS_NAME.put(BigInteger.class.getName(), BigInteger.class);
        CLASS_NAME.put(BigDecimal.class.getName(), BigDecimal.class);
        CLASS_NAME.put(Float.class.getName(), Float.class);
        CLASS_NAME.put(Double.class.getName(), Double.class);
        CLASS_NAME.put(String.class.getName(), String.class);
        CLASS_NAME.put(java.sql.Date.class.getName(), Date.class);
        CLASS_NAME.put(java.sql.Time.class.getName(), Date.class);
        CLASS_NAME.put(java.sql.Timestamp.class.getName(), Date.class);
        CLASS_NAME.put(Object.class.getName(), Object.class);
        CLASS_NAME.put(java.io.InputStream.class.getName(), java.io.InputStream.class);
        CLASS_NAME.put(java.io.Reader.class.getName(), java.io.Reader.class);
        CLASS_NAME.put(java.sql.Blob.class.getName(), java.sql.Blob.class);
        CLASS_NAME.put(java.sql.Clob.class.getName(), java.sql.Clob.class);
        CLASS_NAME.put(java.lang.Short.class.getName(), java.lang.Short.class);
        CLASS_NAME.put(Byte.class.getName(), Byte.class);
        CLASS_NAME.put(byte[].class.getName(), byte[].class);
        CLASS_NAME.put(String[].class.getName(), String[].class);

//        JDBC_TYPE.put("BIT", -7);
//        JDBC_TYPE.put("TINYINT", -6);
//        JDBC_TYPE.put("SMALLINT", 5);
//        JDBC_TYPE.put("INTEGER", 4);
//        JDBC_TYPE.put("BIGINT", -5);
//        JDBC_TYPE.put("FLOAT", 6);
//        JDBC_TYPE.put("REAL", 7);
//        JDBC_TYPE.put("DOUBLE", 8);
//        JDBC_TYPE.put("NUMERIC", 2);
//        JDBC_TYPE.put("DECIMAL", 3);
//        JDBC_TYPE.put("CHAR", 1);
//        JDBC_TYPE.put("VARCHAR", 12);
//        JDBC_TYPE.put("LONGVARCHAR", -1);
//        JDBC_TYPE.put("DATE", 91);
//        JDBC_TYPE.put("TIME", 92);
//        JDBC_TYPE.put("TIMESTAMP", 93);
//        JDBC_TYPE.put("BINARY", -2);
//        JDBC_TYPE.put("VARBINARY", -3);
//        JDBC_TYPE.put("LONGVARBINARY", -4);
//        JDBC_TYPE.put("NULL", 0);
//        JDBC_TYPE.put("OTHER", 1111);
//        JDBC_TYPE.put("JAVA_OBJECT", 2000);
//        JDBC_TYPE.put("DISTINCT", 2001);
//        JDBC_TYPE.put("STRUCT", 2002);
//        JDBC_TYPE.put("ARRAY", 2003);
//        JDBC_TYPE.put("BLOB", 2004);
//        JDBC_TYPE.put("CLOB", 2005);
//        JDBC_TYPE.put("REF", 2006);
//        JDBC_TYPE.put("DATALINK", 70);
//        JDBC_TYPE.put("BOOLEAN", 16);
//        JDBC_TYPE.put("ROWID", -8);
//        JDBC_TYPE.put("NCHAR", -15);
//        JDBC_TYPE.put("NVARCHAR", -9);
//        JDBC_TYPE.put("LONGNVARCHAR", -16);
//        JDBC_TYPE.put("NCLOB", 2011);
//        JDBC_TYPE.put("SQLXML", 2009);
    }


    /**
     * 获取数据库连接
     */
    public static Connection getConnection(String url) throws SQLException {
        try {
            if (url.contains(DatabaseType.MYSQL.name)) {
                Class.forName(DatabaseType.MYSQL.driverClassName);
                databaseType = DatabaseType.MYSQL;
            } else {
                databaseType = DatabaseType.MYSQL;
                Class.forName(DatabaseType.ORACLE.driverClassName);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return DriverManager.getConnection(url);
    }

    private static final String COL_KEY_NAME = "_col_key_name_%@";

    /**
     * 执行查询sql
     */
    public static TableInfo executeQuery(Connection con, String sql, String schemaTable, String infoSql) {
        PreparedStatement statement = null;
        ResultSet resultSet;
        try {
            statement = con.prepareStatement("");
            Map<String, String> infoCom = new HashMap<>();
            TableInfo tableInfo = new TableInfo();
            tableInfo.setName(schemaTable.split("\\.")[1]);

            if (infoSql != null && !infoSql.isEmpty()) {
                ResultSet infoResultSet = statement.executeQuery(String.format(infoSql, schemaTable.split("\\.")[0], schemaTable.split("\\.")[1]));
                while (infoResultSet.next()) {
                    int x = 1;
                    String key = infoResultSet.getString(x++);
                    tableInfo.setComment(infoResultSet.getString(x++));
                    String colName = infoResultSet.getString(x++);
                    infoCom.put(colName, infoResultSet.getString(x));
                    if (key != null && key.equalsIgnoreCase("PRI")) {
                        infoCom.put(COL_KEY_NAME, colName);
                    }
                }
            }

            resultSet = statement.executeQuery(sql.replaceFirst("\\?", schemaTable.split("\\.")[0]).replaceFirst("\\?", schemaTable.split("\\.")[1]));
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                TableInfo.Column column = new TableInfo.Column();
                column.setName(metaData.getColumnName(i));
                String configType = TYPES_CONFIG.getProperty(String.valueOf(metaData.getColumnType(i)));
                if (configType != null) {
                    column.setJavaType(ClassUtils.forName(configType,ClassUtils.class.getClassLoader()));
                } else {
                    Class aClass = getJavaClass(metaData.getColumnClassName(i));
                    if (aClass == null) {
                        column.setJavaType(ClassUtils.forName(metaData.getColumnClassName(i),ClassUtils.class.getClassLoader()));
                    }else {
                        column.setJavaType(aClass);
                    }
                }
                column.setComment(infoCom.get(column.getName()));
                if (column.getName().equals(infoCom.get(COL_KEY_NAME))) {
                    tableInfo.setPrimaryKey(column);
                }
                tableInfo.getColumns().add(column);
            }
            return tableInfo;
        } catch (ClassNotFoundException e) {
            LogUtils.info("jdbcTypes配置错误,不存在class:" + e.getMessage());
        } catch (Exception e) {
            LogUtils.info("数据库错误:" + e.getMessage());
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void setJdbcTypeConfig(String key, String val) {
        TYPES_CONFIG.setProperty(key, val);
    }

    private static Class getJavaClass(String className) throws SQLException {
        return CLASS_NAME.get(className);
    }

    private static Properties getTypeProperties() {
        return new Properties();
    }

    public static TableInfo queryTableInfo(String url, String schemaTable) throws SQLException {
        return executeQuery(getConnection(url), databaseType.tableSQL, schemaTable, databaseType.tableInfoSQL);
    }

    /**
     * 支持的数据库类型
     */
    public static enum DatabaseType {
        ORACLE("oracle", "", "", ""),
        MYSQL("mysql", "com.mysql.jdbc.Driver",
                "SELECT * FROM `?`.`?` where 1=2",
                "SELECT c.COLUMN_KEY column_key,ta.TABLE_COMMENT table_comment, c.COLUMN_NAME column_name,c.COLUMN_COMMENT column_comment\n" +
                        "FROM information_schema.TABLES ta INNER JOIN information_schema.COLUMNS c ON ta.TABLE_SCHEMA = " +
                        "c.TABLE_SCHEMA AND ta.TABLE_NAME = c.TABLE_NAME \n" +
                        "WHERE ta.TABLE_SCHEMA = '%s' AND ta.TABLE_NAME = '%s'");
        public String name;
        public String driverClassName;
        public String tableSQL;
        public String tableInfoSQL;

        DatabaseType(String name, String driverClassName, String tableSQL, String tableInfoSQL) {
            this.name = name;
            this.driverClassName = driverClassName;
            this.tableSQL = tableSQL;
            this.tableInfoSQL = tableInfoSQL;
        }

        @Override
        public String toString() {
            return name;
        }
    }
}
